/*
* Copyright (c) 2017, Kasra Faghihi, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.offbynull.coroutines.javaagent;
import com.offbynull.coroutines.instrumenter.InstrumentationSettings;
import com.offbynull.coroutines.instrumenter.Instrumenter;
import com.offbynull.coroutines.instrumenter.asm.ClassResourceClassInformationRepository;
import com.offbynull.coroutines.instrumenter.generators.DebugGenerators.MarkerType;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.Arrays;
/**
* Java Agent that instruments coroutines.
* @author Kasra Faghihi
*/
public final class CoroutinesAgent {
private CoroutinesAgent() {
// do nothing
}
/**
* Java agent premain.
* @param agentArgs args passed in to agent
* @param inst instrumentation for agent
* @throws NullPointerException if {@code inst} is {@code null}
* @throws IllegalArgumentException if {@code agentArgs} is present but not in the format {@code markerType,debugMode}, or if the passed
* in arguments were not parseable (debugMode must be a boolean and markerType must be a member of {@link MarkerType})
*/
public static void premain(String agentArgs, Instrumentation inst) {
// How do agent args work? http://stackoverflow.com/questions/23287228/how-do-i-pass-arguments-to-a-java-instrumentation-agent
// e.g. java -javaagent:/path/to/agent.jar=argumentstring
MarkerType markerType = MarkerType.NONE;
boolean debugMode = false;
if (agentArgs != null && !agentArgs.isEmpty()) {
String[] splitArgs = agentArgs.split(",");
if (splitArgs.length != 2) {
throw new IllegalArgumentException("Expected argument format is: markerType,debugMode");
}
try {
markerType = MarkerType.valueOf(splitArgs[0]);
} catch (IllegalArgumentException iae) {
throw new IllegalArgumentException("Unable to parse marker type -- must be one of the following: "
+ Arrays.toString(MarkerType.values()), iae);
}
if (splitArgs[1].equalsIgnoreCase("true")) {
debugMode = true;
} else if (splitArgs[1].equalsIgnoreCase("false")) {
debugMode = false;
} else {
throw new IllegalArgumentException("Unable to parse debug mode -- must be true or false");
}
}
inst.addTransformer(new CoroutinesClassFileTransformer(markerType, debugMode));
}
private static final class CoroutinesClassFileTransformer implements ClassFileTransformer {
private final MarkerType markerType;
private final boolean debugMode;
CoroutinesClassFileTransformer(MarkerType markerType, boolean debugMode) {
if (markerType == null) {
throw new NullPointerException();
}
this.markerType = markerType;
this.debugMode = debugMode;
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
// ClassReader cr = new ClassReader(classfileBuffer);
// ClassNode classNode = new SimpleClassNode();
// cr.accept(classNode, 0);
// String classNameFromBytes = classNode.name;
// If class is internal to the coroutines user project, don't instrument them
// FYI: If the class being transformed is a lambda, className will show up as null.
if (className == null || className.startsWith("com/offbynull/coroutines/user/")) {
return null;
}
// If loader is null, don't attempt instrumentation (this is a core class?)
if (loader == null) {
return null;
}
// System.out.println(className + " " + (loader == null));
try {
Instrumenter instrumenter = new Instrumenter(new ClassResourceClassInformationRepository(loader));
byte[] moddedClassfileBuffer = instrumenter.instrument(
classfileBuffer,
new InstrumentationSettings(markerType, debugMode));
return moddedClassfileBuffer;
} catch (Throwable e) {
System.err.println("FAILED TO INSTRUMENT: " + e);
return null;
}
}
}
}